﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace CardGameFramework
{
    public class Deck : Dictionary<int, Card>
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <remarks>
        /// A deck of cards contains Ace through King, with four suits (Spades, Hearts, Clubs, Diamonds).
        /// 
        /// Jokers are not included unless requested.
        /// </remarks>
        /// <param name="deckCount">The number of decks to include.</param>
        /// <param name="includeJokers">Determines if the deck should include Joker's in the decks.</param>
        /// <param name="shuffle">Determines if the deck be shuffle after it is built</param>
        public Deck(int deckCount = 1, bool includeJokers = false, bool shuffle = true)
        {
            Contract.Requires(deckCount > 0);

            IncludeJokers = includeJokers;
            DeckCount = deckCount;
            BuildDeck(shuffle);
        }

         /// <summary>
        /// Are jokers included in the deck?
        /// </summary>
        public bool IncludeJokers { get; private set; }

        /// <summary>
        /// The number of decks included in the deck.
        /// </summary>
        public int DeckCount { get; private set; }
        
        /// <summary>
        /// Builds a new deck.
        /// </summary>
        /// <param name="shuffle">Determines if the deck should be shuffled after it is built.</param>
        public void BuildDeck(bool shuffle = true)
        {
            Contract.Requires(DeckCount > 0);

            Clear();
            var cardCount = 0;
            var rawDeck = new List<Card>();
            for (var i = 0; i < DeckCount; i++)
                rawDeck.AddRange(CreateSingleDeck());

            rawDeck.ForEach(card => Add(++cardCount, card));

            if (shuffle && Count > 0 && DeckCount > 0)
                Shuffle();
        }

        /// <summary>
        /// Shuffles the deck.
        /// </summary>
        public void Shuffle()
        {
            Contract.Requires(Count > 0);
            Contract.Requires(DeckCount > 0);

            var deck = new Deck(DeckCount, IncludeJokers, false);
            var random = new Random();

            var movedCount = 0;
            var usedCardIndexes = new List<int>();
            while (movedCount < Count)
            {
                var randomIndex = random.Next(0, Count);
                if (randomIndex >= 0 
                    && randomIndex < Count 
                    && !usedCardIndexes.Contains(randomIndex))
                {
                    deck.Add(movedCount++, this[randomIndex]);
                    usedCardIndexes.Add(randomIndex);
                }
            }
            Clear();
            deck.ForEach(kvp => Add(kvp.Key, kvp.Value));
        }

        private List<Card> CreateSingleDeck()
        {
            Contract.Ensures(Contract.Result<List<Card>>() != null);

            var list = new List<Card>();
            foreach (var suit in Enum.GetValues(typeof(Suits)))
            {
                if ((Suits)suit == Suits.Joker)
                    continue;

                foreach (var rank in Enum.GetValues(typeof(Ranks)))
                {
                    if ((Ranks)rank == Ranks.LittleJoker || (Ranks)rank == Ranks.BigJoker)
                        continue;
                    list.Add(new Card((Suits) suit, (Ranks) rank));
                }
            }

            if (!IncludeJokers)
            {
                list.Add(new Card(Suits.Joker, Ranks.BigJoker));
                list.Add(new Card(Suits.Joker, Ranks.LittleJoker));
            }
            return list;
        }

        
        



        /// <summary>
        /// Removes all instances of a card from the deck.
        /// </summary>
        /// <param name="suit"></param>
        /// <param name="rank"></param>
        public void RemoveAllCards(Card card)
        {
            var cardIndexes = from cards in this
                    where cards.Value.Suit == card.Suit && cards.Value.Rank == card.Rank
                    select cards.Key;

            cardIndexes.ForEach(i => Remove(i));
        }

    }
}